package org.walkframework.sample.business.common.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.BadSqlGrammarException;
import org.springframework.jdbc.UncategorizedSQLException;
import org.springframework.util.ReflectionUtils;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.walkframework.boot.bean.RespInfo;
import org.walkframework.boot.event.EventSubscriberException;
import org.walkframework.boot.exception.BaseException;
import org.walkframework.boot.i18n.Message;
import org.walkframework.boot.util.ExceptionUtil;
import org.walkframework.boot.util.MapUtil;

import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Map;

/**
 * 公共异常处理
 *
 * @author 尾生
 * @since 2023/3/14
 */
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    private static final Field validateSourceField = resolveSourceField();

    @Value("${walk.product-mode:false}")
    private boolean productMode;

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public RespInfo<?> handleBindException(MethodArgumentNotValidException ex) {
        FieldError fieldError = ex.getBindingResult().getFieldError();
        return RespInfo.errorWithMsg(null != fieldError ? resolveMessage(fieldError) : "数据不符合要求");
    }

    private String resolveMessage(FieldError fieldError) {
        if (null == validateSourceField) {
            return fieldError.getDefaultMessage();
        }
        Object source = ReflectionUtils.getField(validateSourceField, fieldError);
        if (source instanceof ConstraintViolation) {
            String messageTemplate = ((ConstraintViolation) source).getMessageTemplate();
            if (StringUtils.startsWith(messageTemplate, "{java")) {
                return fieldError.getField() + fieldError.getDefaultMessage();
            }
        }
        return fieldError.getDefaultMessage();
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Object handleCommonException(Exception ex, HttpServletResponse response) throws IOException {
        response.setContentType("application/json;charset=UTF-8");
        Throwable e;
        if (ex instanceof EventSubscriberException) {
            e = ex.getCause();
        } else {
            e = ex;
        }
        RespInfo<Map<String, Object>> resp;
        if (e instanceof UncategorizedSQLException || e instanceof BadSqlGrammarException) {
            resp = RespInfo.errorWithMsg("服务运行异常");
            if (!productMode) {
                resp.setData(MapUtil.map("sql", StringUtils.abbreviate(e.getCause().getMessage(), 500)));
            }
        } else if (e instanceof DataAccessException) {
            resp = RespInfo.errorWithMsg("服务运行异常，数据操作失败！");
            if (!productMode) {
                resp.setData(MapUtil.map("sql", StringUtils.abbreviate(e.getMessage(), 500)));
            }
        } else if (e instanceof BaseException) {
            resp = RespInfo.errorWithMsg(e.getMessage());
        } else {
            resp = RespInfo.errorWithMsg(Message.defaultError().getDisplayText());
            if (!productMode) {
                resp.setData(MapUtil.map("exception", ExceptionUtil.getBriefInfo(e)));
            }
        }
        if (!(e instanceof BaseException)) {
            // 打印未知异常
            log.error("Error execute api for ", e);
        }
        return resp;
    }


    private static Field resolveSourceField() {
        Field field = ReflectionUtils.findField(FieldError.class, "source");
        if (null != field) {
            field.setAccessible(true);
        }
        return field;
    }
}
